﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web.Mvc;

using Lucene.Net.QueryParsers;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Search;
using Lucene.Net.Analysis;
using Lucene.Net.Index;

using ERPStore.Extensions;
using ERPStore.Web.Extensions;
using ERPStore.Indexers.Lucene.Models;

using Microsoft.Practices.Unity;

using CloudSoft.Extensions;

namespace ERPStore.Indexers.Lucene.Services
{
	public class CatalogService : ERPStore.Services.ICatalogService
	{
		private global::Lucene.Net.Util.Version m_LuceneVersion;

		public CatalogService(
			ERPStore.Services.ICatalogService connectorCatalogService
			, Logging.ILogger logger
			, Analyzer analyzer
			, Models.Settings settings
			, Repositories.LuceneRepository luceneRepository)
		{
			this.IndexPath = System.IO.Path.Combine( settings.Path, "Catalog");
			this.ConnectorCatalogService = connectorCatalogService;
			this.Logger = logger;
			this.Analyzer = analyzer;
			m_LuceneVersion = global::Lucene.Net.Util.Version.LUCENE_29;
			this.CacheService = GlobalConfiguration.Configuration.DependencyResolver.GetService<ERPStore.Services.ICacheService>();
			this.Settings = settings;
			this.LuceneRepository = luceneRepository;
			this.EventPublisherService = GlobalConfiguration.Configuration.DependencyResolver.GetService<EventBrokR.IPublisher>();
		}

		internal string IndexPath { get; set; }
		protected ERPStore.Services.ICatalogService ConnectorCatalogService { get; set; }
		protected Logging.ILogger Logger { get; private set; }
		internal Analyzer Analyzer { get; private set; }
		protected ERPStore.Services.ICacheService CacheService { get; private set; }
		protected Models.Settings Settings { get; private set; }
		protected Repositories.LuceneRepository LuceneRepository { get; private set; }
		protected EventBrokR.IPublisher EventPublisherService { get; private set; }

		public string Name
		{
			get
			{
				return "Lucene";
			}
		}

		public ERPStore.Services.ICatalogService GetConnectorCatalogService()
		{
			return ConnectorCatalogService;
		}

		internal string GetConnectorCatalogServiceName()
		{
			return ConnectorCatalogService.Name;
		}

		#region ICatalogService Members

		public IList<ERPStore.Models.Product> GetCrossSellingList(int productId)
		{
			return ConnectorCatalogService.GetCrossSellingList(productId);
		}

		public IList<ERPStore.Models.Product> GetCrossSellingList(ERPStore.Models.OrderCart cart)
		{
			return ConnectorCatalogService.GetCrossSellingList(cart);
		}

		public ERPStore.Models.Product GetHeadProductOfCategory(int productCategoryId)
		{
			return ConnectorCatalogService.GetHeadProductOfCategory(productCategoryId);
			//var category = GetCategoryById(productCategoryId);
			//var categoryIdList = category.Children.DeepSelect(i => i.Id).ToList();
			//categoryIdList.Add(productCategoryId);
			//var categoryQuery = new BooleanQuery();
			//foreach (var item in categoryIdList)
			//{
			//    // search += string.Format(" OR CategoryId:{0}", item);
			//    categoryQuery.Add(new TermQuery(new Term("CategoryId", item.ToString())), BooleanClause.Occur.SHOULD);
			//}

			//var topSellQuery = new TermQuery(new Term("IsTopSell", "1"));

			//var searchQuery = new BooleanQuery();
			//searchQuery.Add(categoryQuery, BooleanClause.Occur.MUST);
			//searchQuery.Add(topSellQuery, BooleanClause.Occur.MUST);

			//int count = 0;
			//var list = GetProductList(searchQuery, 0, 20, out count, null);
			//Models.Product product = null;
			//if (list.IsNotNullOrEmpty())
			//{
			//    var productList = list.Where(i => i.IsTopSell);
			//    if (productList.IsNotNullOrEmpty())
			//    {
			//        var rnd = new Random();
			//        var index = rnd.Next(productList.Count());
			//        product = productList.ToList()[index];
			//        return product;
			//    }
			//    if (product == null)
			//    {
			//        product = list.First();
			//    }
			//}
			//return null;
		}

		public ERPStore.Models.Product GetProductByCode(string productCode)
		{
			if (productCode.IsNullOrTrimmedEmpty())
			{
				return null;
			}
			var q = string.Format("Code:{0}", productCode);
			var parser = new QueryParser(m_LuceneVersion, "Code", new StandardAnalyzer(m_LuceneVersion));
			int count = 0;
			var list = GetProductList(parser.Parse(productCode), 0, int.MaxValue, out count, null, true, true);
			if (list.IsNotNullOrEmpty())
			{
				return list.First();
			}
			return null;
		}

		public ERPStore.Models.Product GetVariationProductByCode(string productCode)
		{
			if (productCode.IsNullOrTrimmedEmpty())
			{
				throw new ArgumentException("productCode does not be null or empty");
			}
			var q = string.Format("Code:{0}", productCode);
			var parser = new QueryParser(m_LuceneVersion, "Code", new StandardAnalyzer(m_LuceneVersion));
			int count = 0;
			var list = GetProductList(parser.Parse(productCode), 0, int.MaxValue, out count, null, true, false);
			if (list.IsNotNullOrEmpty())
			{
				return list.First();
			}
			return null;
		}


		public virtual void ReloadProduct(string productCode)
		{
			var product = ConnectorCatalogService.GetProductByCode(productCode);
			if (product == null)
			{
				return;
			}

			product = ConnectorCatalogService.GetProductById(product.Id);
			// product.Keywords = GetProductKeywords(product);
			var oldProduct = GetProductById(product.Id);

			EventPublisherService.PublishAsync(new ERPStore.Web.Events.ReloadProductEvent()
			{
				NewProduct = product,
				OldProduct = oldProduct,
			});

			CloudSoft.Workflows.WorkflowQueueUserWorkItem.RunAsync(
				new Workflows.Activities.ReloadProduct(),
				new Dictionary<string, object>() { { "Product", product }, { "LuceneVersion", m_LuceneVersion } },
				null,
				null,
				(ex) =>
					{
						Logger.Error(ex);
					}
				);
		}

		public ERPStore.Models.Product GetProductById(int productId)
		{
			var q = string.Format("Id:{0}", productId);
			var parser = new QueryParser(m_LuceneVersion, "Id", new StandardAnalyzer(m_LuceneVersion));
			int count = 0;
			var list = GetProductList(parser.Parse(productId.ToString()), 0, int.MaxValue, out count, null, true, true);
			if (list.IsNotNullOrEmpty())
			{
				return list.First();
			}
			return null;
		}

		public ERPStore.Models.Product GetVariationProductById(int productId)
		{
			var q = string.Format("Id:{0}", productId);
			var parser = new QueryParser(m_LuceneVersion, "Id", new StandardAnalyzer(m_LuceneVersion));
			int count = 0;
			var list = GetProductList(parser.Parse(productId.ToString()), 0, int.MaxValue, out count, null, true, false);
			if (list.IsNotNullOrEmpty())
			{
				return list.First();
			}
			return null;
		}

		public IList<ERPStore.Models.Product> GetProductList(int index, int pageSize, out int count)
		{
			throw new NotImplementedException();
		}

		//public IList<ERPStore.Models.Product> GetProductListByCategoryId(int categoryId, NameValueCollection parameters, int index, int pageSize, out int count)
		//{
		//    var category = GetCategoryById(categoryId);
		//    var childrenIdList = category.Children.DeepSelect(i => i.Id);
		//    var search = string.Format(" CategoryId:{0}", categoryId);
		//    foreach (var item in childrenIdList)
		//    {
		//        search += string.Format(" OR CategoryId:{0}", item);
		//    }

		//    var defaultSortList = ConnectorCatalogService.CreateDefaultSortList();
		//    var sort = CreateSort(defaultSortList);

		//    var parser = new QueryParser(m_LuceneVersion, "CategoryId", new WhitespaceAnalyzer());
		//    var result = GetProductList(parser.Parse(search), index, pageSize, out count, sort, true, true);

		//    return result;
		//}

		public IList<ERPStore.Models.PropertyGroup> GetProductExtendedPropertyListByCategoryId(int categoryId)
		{
			return ConnectorCatalogService.GetProductExtendedPropertyListByCategoryId(categoryId);
		}

		public ERPStore.Models.ProductSearchSubstitution SubstituteSearchTerms(string term)
		{
			return ConnectorCatalogService.SubstituteSearchTerms(term);
		}

		public IList<ERPStore.Models.Product> GetProductListBySearch(ERPStore.Models.ProductListFilter filter, int index, int pageSize, out int count)
		{
			if (!filter.IsEmpty())
			{
				filter.RemoveVariant = true;
			}

			// Pas de fulltext dans le cas d'une selection
			if (filter.SelectionId.HasValue)
			{
				return ConnectorCatalogService.GetProductListBySearch(filter, index, pageSize, out count);
			}

			IList<ERPStore.Models.Product> result = null;
			var loop = 0;
			var ope = QueryParser.Operator.AND;
			while (true)
			{
				Logger.Debug("FullTextIndex.GetProductListBySearch {0}", filter.ToString());
				var query = CreateQueryByProductListFilter(filter, ope);
				if (filter.HasStock.HasValue)
				{
					filter.SortList.RemoveAll(i => i.PropertyName.Equals("HasStock", StringComparison.InvariantCultureIgnoreCase));
				}
				var sorting = CreateSort(filter.SortList);
				bool sortByScore = filter.SortByScore;
				result = GetProductList(query, index, pageSize, out count, sorting, false, true, sortByScore);
				if (filter.Search.IsNullOrTrimmedEmpty() 
					|| filter.Search.Words().Length <= 1
					|| result.Count > 0
					|| loop > 0)
				{
					break;
				}
				// on passe au OR
				ope = QueryParser.Operator.OR;
				loop++;
			}
			return result;
		}

		public IList<ERPStore.Models.Product> GetProductListByCustomer(ERPStore.Models.ProductListFilter filter, int index, int pageSize, out int count)
		{
			return ConnectorCatalogService.GetProductListByCustomer(filter, index, pageSize, out count);
		}

		public IList<ERPStore.Models.PropertyGroup> GetProductExtendedPropertyListBySearch(ERPStore.Models.ProductListFilter filter)
		{
			return ConnectorCatalogService.GetProductExtendedPropertyListBySearch(filter);
		}

		//public IList<ERPStore.Models.Product> GetProductListByBrandId(int brandId, NameValueCollection parameters, int index, int pageSize, out int count)
		//{
		//    var query = string.Format("BrandId:{0}", brandId);
		//    var parser = new QueryParser(m_LuceneVersion, "BrandId", new StandardAnalyzer(m_LuceneVersion));

		//    var defaultSortList = ConnectorCatalogService.CreateDefaultSortList();
		//    var sort = CreateSort(defaultSortList);

		//    return GetProductList(parser.Parse(brandId.ToString()), index, pageSize, out count, sort, true, false);
		//}

		public IList<ERPStore.Models.PropertyGroup> GetProductExtendedPropertyListByBrandId(int brandId)
		{
			return GetProductExtendedPropertyListByBrandId(brandId);
		}

		public IList<ERPStore.Models.Product> GetProductListByIdList(IEnumerable<int> productIdList, bool useSubstitution)
		{
			if (productIdList.IsNullOrEmpty())
			{
				return new List<ERPStore.Models.Product>();
			}

			var result = new List<ERPStore.Models.Product>();
			int index = 0;
			int pageSize = 100;
			var parser = new QueryParser(m_LuceneVersion, "Id", new WhitespaceAnalyzer());
			// Prevent "too many boolean clause"
			while (true)
			{
				var idList = productIdList.Skip(index * pageSize).Take(pageSize);
				if (idList.IsNullOrEmpty())
				{
					break;
				}
				index++;
				string search = null;
				foreach (var item in idList)
				{
					search += string.Format(" Id:{0}", item);
				}
				int count = 0;
				var list = GetProductList(parser.Parse(search), 0, int.MaxValue, out count, null, true, useSubstitution);
				if (!list.IsNullOrEmpty())
				{
					result.AddRange(list);
				}
			}
			return result;
		}

		public IList<ERPStore.Models.ProductRelation> GetProductRelations(int productId)
		{
			return ConnectorCatalogService.GetProductRelations(productId);
		}

		public IList<ERPStore.Models.ProductStockInfo> GetProductStockInfoList(IEnumerable<int> productIdList)
		{
			return ConnectorCatalogService.GetProductStockInfoList(productIdList);
		}

		public ERPStore.Models.ProductStockInfo GetProductStockInfo(ERPStore.Models.Product product)
		{
			return ConnectorCatalogService.GetProductStockInfo(product);
		}

		public ERPStore.Models.ProductStockInfo GetProductStockInfo(string productCode)
		{
			return ConnectorCatalogService.GetProductStockInfo(productCode);
		}

		public IList<ERPStore.Models.PropertyGroup> GetProductPropertyGroupList()
		{
			return ConnectorCatalogService.GetProductPropertyGroupList();
		}

		public IList<ERPStore.Models.ProductCategory> GetCategories()
		{
			return ConnectorCatalogService.GetCategories();
		}

		public IList<ERPStore.Models.ProductCategory> GetForefrontCategories()
		{
			return ConnectorCatalogService.GetForefrontCategories();
		}

		public ERPStore.Models.ProductCategory GetCategoryByCode(string categoryCode)
		{
			return ConnectorCatalogService.GetCategoryByCode(categoryCode);
		}

		public ERPStore.Models.ProductCategory GetCategoryByLink(string link)
		{
			return ConnectorCatalogService.GetCategoryByLink(link);
		}

		public ERPStore.Models.ProductCategory GetCategoryById(int categoryId)
		{
			return ConnectorCatalogService.GetCategoryById(categoryId);
		}

		public void ReloadCategories()
		{
			ConnectorCatalogService.ReloadCategories();
		}

		public IList<ERPStore.Models.ProductCategory> GetProductCategoryListByBrandId(int brandId)
		{
			return ConnectorCatalogService.GetProductCategoryListByBrandId(brandId);
		}

		public IList<ERPStore.Models.ProductCategory> GetProductCategoryListBySearch(ERPStore.Models.ProductListFilter search)
		{
			return ConnectorCatalogService.GetProductCategoryListBySearch(search);
			/*
			IList<ERPStore.Models.ProductCategory> result = null;

			int count = 0;

			// Recupération de la liste complete des produits
			var list = GetProductListBySearch(search, 0, int.MaxValue, out count);

			// Recherche des id de categorie distincts
			var categoryIdList = list.Where(i => i.Category != null)
									.GroupBy(i => i.Category.Id)
									.ToList();

			result = GetCategories().ToClonedList();

			// Mise à jour du nombre de produits à 0
			result.Traverse(i => { i.ProductCount = 0; i.IsSelected = false; });

			// Creation de la liste de resultat
			foreach (var categoryGroup in categoryIdList)
			{
				int categoryId = categoryGroup.Key;
				int c = categoryGroup.Count();
				var category = result.DeepFirst(i => i.Id == categoryId);
				if (category != null)
				{
					category.ProductCount = c;
					// Selection jusqu'a la categorie root
					result.ActionToRoot(category, i => { i.IsSelected = true; });
				}
			}

			result.DeepRemoveAll(i => !i.IsSelected || i.DeepProductCount == 0);

			return result;
			 */
		}

		public IList<ERPStore.Models.Brand> GetBrands()
		{
			return ConnectorCatalogService.GetBrands();
		}

		public ERPStore.Models.Brand GetBrandById(int? brandId)
		{
			return ConnectorCatalogService.GetBrandById(brandId);
		}

		public ERPStore.Models.Brand GetBrandByLink(string link)
		{
			return ConnectorCatalogService.GetBrandByLink(link);
		}

		public IList<ERPStore.Models.Brand> GetTopSellBrands()
		{
			throw new NotImplementedException();
		}

		public IList<ERPStore.Models.Brand> GetBrandListForefront()
		{
			return ConnectorCatalogService.GetBrandListForefront();
		}

		public void ReloadBrands()
		{
			ConnectorCatalogService.ReloadBrands();
		}

		public IList<ERPStore.Models.Brand> GetBrandListByProductCategoryId(int productCategoryId)
		{
			return ConnectorCatalogService.GetBrandListByProductCategoryId(productCategoryId);
		}

		public IList<ERPStore.Models.Brand> GetBrandListBySearch(ERPStore.Models.ProductListFilter search)
		{
			return ConnectorCatalogService.GetBrandListBySearch(search);
			/*
			IList<ERPStore.Models.Brand> result = null;

			int count = 0;
			// Recuperation de la liste complete des produits
			var list = GetProductListBySearch(search, 0, int.MaxValue, out count);

			var brandIdList = list.Where(i => i.Brand != null)
									.GroupBy(i => i.Brand.Id)
									.ToList();

			result = GetBrands().ToClonedList();

			foreach (var brandGroup in brandIdList)
			{
				int c = brandGroup.Count();
				var brand = result.FirstOrDefault(i => i.Id == brandGroup.Key);
				if (brand != null)
				{
					brand.ProductCount = c;
					brand.IsSelected = true;
				}
			}

			result.RemoveAll(i => !i.IsSelected);

			return result;
			 */
		}

		public ERPStore.Models.Brand GetBrandByName(string brandName)
		{
			return ConnectorCatalogService.GetBrandByName(brandName);
		}

		public NameValueCollection RemoveNotFilteredParameters(NameValueCollection input)
		{
			return ConnectorCatalogService.RemoveNotFilteredParameters(input);
		}

		public IEnumerable<ERPStore.Models.Profession> GetProfessionList()
		{
			return ConnectorCatalogService.GetProfessionList();
		}

		public IEnumerable<ERPStore.Models.SearchTerm> GetTopSearchTermList(int count)
		{
			return ConnectorCatalogService.GetTopSearchTermList(count);
		}

		public ERPStore.Models.ProductListFilter CreateProductListFilter(IEnumerable<ERPStore.Models.WebParameter> nvc)
		{
			return ConnectorCatalogService.CreateProductListFilter(nvc);
		}

		public IList<ERPStore.Models.SortProperty> CreateDefaultSortList(IEnumerable<ERPStore.Models.WebParameter> nvc)
		{
			return ConnectorCatalogService.CreateDefaultSortList(nvc);
		}

		public string GetProductKeywords(ERPStore.Models.Product product)
		{
			return ConnectorCatalogService.GetProductKeywords(product);
		}

		public IList<ERPStore.Models.EntitySelection> GetProductSelectionList()
		{
			return ConnectorCatalogService.GetProductSelectionList();
		}

		public bool SelectionContainsProduct(int selectionId, int productId)
		{
			return ConnectorCatalogService.SelectionContainsProduct(selectionId, productId);
		}

		public ERPStore.Models.ProductVariation GetProductVariationListByProductId(int parentProductId)
		{
			return ConnectorCatalogService.GetProductVariationListByProductId(parentProductId);
		}

		public IList<ERPStore.Models.VariationValue> GetVariationValueListByProductId(int productId)
		{
			return ConnectorCatalogService.GetVariationValueListByProductId(productId);
		}

		public ERPStore.Models.Product GetDefaultVariantByProductVariationId(int variationId)
		{
			return ConnectorCatalogService.GetDefaultVariantByProductVariationId(variationId);
		}

		public IList<ERPStore.Models.Product> ApplySort(IEnumerable<ERPStore.Models.Product> list)
		{
			return ConnectorCatalogService.ApplySort(list);
		}

		public Dictionary<string, string> GetAutocompleteSearch(string input, int count)
		{
			if (input == null)
			{
				return new Dictionary<string, string>();
			}

			input = input.Trim();
			input = input.Replace("*", "");
			input = input.Replace(":", "");
			input = input.Replace("+", "");
			input = input.Replace("(", " ");
			input = input.Replace(")", " ");
			input = input.Replace("^", "");
			input = input.Replace("\"", "");
			input = input.Replace(")", "");
			input = input.Replace("}", "");
			input = input.Replace("{", "");
			input = input.Trim();

			var autocompleteMode = Settings.AutocompleteMode;
			Query query = null;
			if (autocompleteMode == "word")
			{
				query = GetAutocompletWordStartSearch(input);
			}
			else
			{
				query = GetAutocompletePhraseStartSearch(input);
			}

			var collector = TopScoreDocCollector.create(count, false);
			var directoryInfo = new System.IO.DirectoryInfo(IndexPath);
			var lockFactory = new global::Lucene.Net.Store.NoLockFactory();
			var directory = global::Lucene.Net.Store.FSDirectory.Open(directoryInfo, lockFactory);

			var searcher = new IndexSearcher(directory, true);
			searcher.Search(query, collector);

			var scoreDocs = collector.TopDocs().ScoreDocs;
			var result = new Dictionary<string,string>();
			foreach (var hit in scoreDocs)
			{
				var doc = searcher.Doc(hit.doc);
				var title = doc.Get("Title");
				if (!result.ContainsKey(title))
				{
					result.Add(title, doc.Get("Code"));
				}
			}

			return result;
		}

		private Query GetAutocompletePhraseStartSearch(string input)
		{
			input = input.Replace(" ", "xxx");

			var analyzer = new WhitespaceAnalyzer();
			var parser = new QueryParser(m_LuceneVersion, "BlocTitle", analyzer);
			var query = parser.Parse(string.Format("{0}*", input));

			return query;
		}

		private Query GetAutocompletWordStartSearch(string input)
		{
			var analyzer = new WhitespaceAnalyzer();
			var parser = new QueryParser(m_LuceneVersion, "Title", analyzer);
			var query = parser.Parse(string.Format("{0}*", input));

			return query;
		}

		#endregion

		public virtual IList<ERPStore.Models.Product> GetProductList(Query query, int page, int pageSize, out int count, Sort sort, bool useScoreDoc, bool substituteVariation, bool sortByScore = true)
		{
			count = 0;
			if (query == null)
			{
				return new List<ERPStore.Models.Product>();
			}

			var docList = LuceneRepository.GetDocumentList(query, page, pageSize, out count, sort, useScoreDoc, substituteVariation, sortByScore);
			if (docList == null)
			{
				return new List<ERPStore.Models.Product>();
			}

			var result = (from doc in docList
						  select doc.ToProduct()).ToList();

			// Remplacer les produits de type variation par le produit variant par defaut
			if (substituteVariation)
			{
				if (!result.IsNullOrEmpty())
				{
					SubstituteVariations(result);
				}
			}

			return result;
		}

		public Models.LuceneDocumentListResult GetDocumentList(Models.LuceneDocumentListFilter filter)
		{
			if (filter == null)
			{
				return null;
			}
			var directoryInfo = new System.IO.DirectoryInfo(IndexPath);
			var lockFactory = new global::Lucene.Net.Store.NoLockFactory();
			var directory = global::Lucene.Net.Store.FSDirectory.Open(directoryInfo, lockFactory);

			var result = new Models.LuceneDocumentListResult();

			var searcher = new IndexSearcher(directory, true);
			TopDocsCollector collector = null;
			var numHits = Math.Min((filter.PageIndex + 1) * filter.PageSize, searcher.MaxDoc());
			collector = TopScoreDocCollector.create(numHits, false);
			// var escapeSearch = new QueryParser.Escape(filter.SearchTerm);
			var analyser = new StandardAnalyzer(m_LuceneVersion);
			var fields = new List<string>()
				{
				    "Title",
				    "Code",
					"ManufacturerPartNumber",
				    "Keywords",
				    "ShortDescription",
				    "LongDescription",
				    "StrengthsPoints",
					"GTIN.EAN",
					"GTIN.UPC",
				};

			var q = new BooleanQuery();
			foreach (var field in fields)
			{
				var fq = new QueryParser(m_LuceneVersion, field, analyser).Parse(filter.SearchTerm);
				q.Add(fq, BooleanClause.Occur.SHOULD);
			}

			searcher.Search(q, collector);

			var count = collector.GetTotalHits();

			IEnumerable<ScoreDoc> docs = collector.TopDocs().ScoreDocs;
			docs = docs.ToList();

			var list = docs.Skip(filter.PageIndex * filter.PageSize)
							.Take(filter.PageSize)
							.Select(sd => searcher.Doc(sd.doc))
							.ToList();

			foreach (var document in list)
			{
				var ldoc = new Models.LuceneDocument();
				ldoc.Boost = document.GetBoost();
				foreach (global::Lucene.Net.Documents.Field field in document.GetFields())
				{
					var lfield = new Models.LuceneField();
					lfield.FieldName = field.Name();
					lfield.Value = document.Get(lfield.FieldName);
					ldoc.FieldList.Add(lfield);
				}
				result.List.Add(ldoc);
			}

			return result;
		}

		private void SubstituteVariations(List<ERPStore.Models.Product> result)
		{
			while (true)
			{
				var parentVariation = result.Where(i => i != null).FirstOrDefault(i => i.IsParentVariation);
				if (parentVariation == null)
				{
					break;
				}
				var index = result.IndexOf(parentVariation);

				var variant = ConnectorCatalogService.GetDefaultVariantByProductVariationId(parentVariation.Id);
				if (variant != null)
				{
					result[index] = variant; // Remplacement de la variation
				}
				else
				{
					// Ne devrait pas se produire
					// Uniquement s'il s'agit d'une variation
					// sans variant
					result.Remove(parentVariation);
				}
			}
		}

		internal global::Lucene.Net.Search.Sort CreateSort(IList<ERPStore.Models.SortProperty> sort)
		{
			var sortFields = new List<SortField>();
			Sort result = null;
			if (!sort.IsNullOrEmpty())
			{
				foreach (var item in sort)
				{
					var typeId = SortField.STRING; // Auto
					if (item.Type == typeof(long)
						|| item.Type == typeof(decimal))
					{
						typeId = SortField.LONG;
					}
					else if (item.Type == typeof(bool))
					{
						typeId = SortField.INT;
					}
					else if (item.Type == typeof(System.Int32))
					{
						typeId = SortField.INT;
					}

					var sortField = new SortField(item.PropertyName, typeId, item.Direction == System.ComponentModel.ListSortDirection.Descending);
					sortFields.Add(sortField);
				}
				result = new Sort(sortFields.ToArray());
			}

			return result;
		}

		internal global::Lucene.Net.Search.Query CreateQueryByProductListFilter(ERPStore.Models.ProductListFilter filter, QueryParser.Operator ope)
		{
			Logger.Debug("FullTextIndex.CreateQueryByProductListFilter {0}", filter.ToString());
			var bquery = new BooleanQuery();

			BooleanQuery searchQuery = null;
			if (!filter.Search.IsNullOrTrimmedEmpty())
			{
				var constraint = filter.Search;
				constraint = constraint.Trim();
				constraint = constraint.Replace(":", "");

				searchQuery = new BooleanQuery();

				var words = GetWordList(constraint);
				var fields = new Dictionary<string, float>()
				{
				    { "Title", 10f },
				    { "Code" , 8f},
					{ "ManufacturerPartNumber", 7f},
				    { "Keywords", 6f},
				    { "ShortDescription", 4f},
				    { "LongDescription", 4f},
				    { "StrengthsPoints", 4f},
					{ "GTIN.EAN", 2f},
					{ "GTIN.UPC", 2f},
				};

				var occur = (ope == QueryParser.Operator.OR) ? BooleanClause.Occur.SHOULD : BooleanClause.Occur.MUST;
				foreach (var field in fields)
				{
					var fieldQuery = new BooleanQuery();
					var wordBoost = field.Value;
					for (int i = 0; i < words.Count; i++)
					{
						var word = words[i];
						word = System.Text.RegularExpressions.Regex.Replace(word, @"\W*", "");
						var term = new Term(field.Key, word);
						// Query q = new TermQuery(term);
						var parser = new QueryParser(m_LuceneVersion, field.Key, Analyzer);
						if (word.IsNullOrTrimmedEmpty())
						{
							continue;
						}
						Query q = parser.Parse(word);

						if (field.Key == "Keywords"
							&& !word.EndsWith("*"))
						{
							word = word + "*";
						}

						if (word.EndsWith("*"))
						{
							var w = word.Replace("*", string.Empty);
							var prefixTerm = new Term(field.Key, w);
							q = new PrefixQuery(prefixTerm);
						}
						else if (word.IndexOf("*") != -1
							|| word.IndexOf("?") != -1)
						{
							q = new WildcardQuery(term);
						}
						else if (word.IndexOf("~") != -1)
						{
							var w = word.Replace("~", string.Empty);
							term = new Term(field.Key, w);
							q = new FuzzyQuery(term);
						}
						q.SetBoost(wordBoost);
						fieldQuery.Add(q, occur);
						wordBoost = Math.Max(wordBoost - 0.05f, 0f);
					}
					searchQuery.Add(fieldQuery, BooleanClause.Occur.SHOULD);
				}
				if (filter.HasStock.HasValue
					|| filter.ProductCategoryId.HasValue
					|| filter.BrandId.HasValue
					|| filter.ShippingFree.HasValue
					|| filter.MaxSalePrice.HasValue
					|| filter.MinSalePrice.HasValue
					|| filter.HasPicture.HasValue)
				{
					bquery.Add(searchQuery, BooleanClause.Occur.MUST);
				}
				else
				{
					bquery.Add(searchQuery, BooleanClause.Occur.SHOULD);
				}
			}

			if (filter.ExtendedParameters != null 
				&& filter.ExtendedParameters.Count > 0)
			{
				foreach (string name in filter.ExtendedParameters.Keys)
				{
					var parameterQuery = new TermQuery(new Term(string.Format("ExtendedProperties.{0}", name), filter.ExtendedParameters[name]));
					parameterQuery.SetBoost(1f);
					bquery.Add(parameterQuery, BooleanClause.Occur.SHOULD);
				}
			}

			if (filter.RemoveVariant)
			{
				var variantQuery = new TermQuery(new Term("IsVariant", "1"));
				bquery.Add(variantQuery, BooleanClause.Occur.MUST_NOT);
			}

			if (filter.RemoveParentVariation)
			{
				var parentVariationQuery = new TermQuery(new Term("IsParentVariation", "1"));
				bquery.Add(parentVariationQuery, BooleanClause.Occur.MUST_NOT);
			}

			if (filter.ProductCategoryId.HasValue)
			{
				var categoryQuery = new BooleanQuery();

				var category = GetCategoryById(filter.ProductCategoryId.Value);
				// Recherche des ids de tous les enfants
				var childrenIdList = category.Children.DeepSelect(i => i.Id);
				var cQuery = new TermQuery(new Term("CategoryId", filter.ProductCategoryId.Value.ToString()));
				cQuery.SetBoost(1f);
				categoryQuery.Add(cQuery, BooleanClause.Occur.SHOULD);
				foreach (var item in childrenIdList)
				{
					var subCategoryQuery = new TermQuery(new Term("CategoryId", item.ToString()));
					subCategoryQuery.SetBoost(1f);
					categoryQuery.Add(subCategoryQuery, BooleanClause.Occur.SHOULD);
				}
				bquery.Add(categoryQuery, BooleanClause.Occur.MUST);
			}


			if (filter.BrandId.HasValue)
			{
				var brandQuery = new TermQuery(new Term("BrandId", filter.BrandId.Value.ToString()));
				brandQuery.SetBoost(1f);
				bquery.Add(brandQuery, BooleanClause.Occur.MUST);
			}

			if (filter.HasStock.HasValue)
			{
				var stockQuery = new TermQuery(new Term("HasStock", filter.HasStock.Value ? "1" : "0"));
				bquery.Add(stockQuery, BooleanClause.Occur.MUST);
			}

			if (filter.ShippingFree.HasValue
				|| filter.MinSalePrice.HasValue
				|| filter.MaxSalePrice.HasValue)
			{
				var shipping = GlobalConfiguration.Configuration.Settings.Shipping.DeliveryCountryList.Single(i => i.Country == GlobalConfiguration.Configuration.Settings.Country);
				var minSalePrice = filter.MinSalePrice.GetValueOrDefault(0);
				if (filter.ShippingFree.HasValue)
				{
					minSalePrice = Convert.ToInt32(Math.Max(minSalePrice, shipping.MinimalFreeOfCarriageAmount));
				}
				var maxSalePrice = filter.MaxSalePrice.GetValueOrDefault(100000);
				var lowerTerm = string.Format("{0:000000000000000}", minSalePrice * 1000000);
				var upperTerm = string.Format("{0:000000000000000}", maxSalePrice * 1000000);
				var amountQuery = new TermRangeQuery("SalePriceSort", lowerTerm, upperTerm, true, true);
				bquery.Add(amountQuery, BooleanClause.Occur.MUST);
			}

			if (filter.SelectionId.HasValue)
			{
				bquery.Add(new TermQuery(new Term("SelectionId:{0}", "1")), BooleanClause.Occur.MUST);
			}

			// Supression des produits non vendables
			if (bquery.GetClauses().Length > 0
				&& GlobalConfiguration.Configuration.Settings.Catalog.AllowNotSellableInSearchResult)
			{
				var isNotSellableQuery = new TermQuery(new Term("IsSellable", "0"));
				bquery.Add(isNotSellableQuery, BooleanClause.Occur.MUST_NOT);
			}

			if (bquery.GetClauses().Length == 0)
			{
				return new MatchAllDocsQuery();
			}

			return bquery;
		}

		public static List<string> GetWordList(string value)
		{
			if (value.IsNullOrTrimmedEmpty())
			{
				return new List<string>();
			}

			// Suppression des espaces avant/après
			var workingValue = value.Trim();
			var result = new List<string>();
			// Suppression des doubles espaces

			if (value.IndexOf(@"\""") == -1)
			{
				// Recupération des expression entre guillemets
				var reg = new System.Text.RegularExpressions.Regex(@"""(?<keyword>[^""]*?)""", System.Text.RegularExpressions.RegexOptions.Singleline);
				var kwid = 0;
				var phraseList = new List<string>();
				foreach (System.Text.RegularExpressions.Match item in reg.Matches(workingValue))
				{
					var kw = item.Groups["keyword"].Value;
					phraseList.Add(kw);
					workingValue = workingValue.Replace("\"" + kw + "\"", string.Format("[{0}]",kwid));
					kwid++;
				}
				result = workingValue.Split(' ').ToList();

				for (int i = 0; i < phraseList.Count; i++)
				{
					var phrase = phraseList[i];
					if (phrase == null)
					{
						break;
					}
					var pattern = string.Format("[{0}]", i);
					var item = result.FirstOrDefault(kw => kw.Equals(pattern));
					if (item != null)
					{
						var itemId = result.IndexOf(item);

						item = item.Replace(item, "\"" + phrase + "\"");
						result[itemId] = item;
					}
				}
			}
			else
			{
				result = workingValue.Split(' ').ToList();
			}

			// suppression des espaces seuls
			result.RemoveAll(i => i == " ");
			return result;
		}


	}
}
